package com.zhanglinwei.openApiClient.wrapper;

import com.zhanglinwei.common.utils.AssertUT;
import com.zhanglinwei.openApiClient.enums.SQLFunction;
import com.zhanglinwei.openApiClient.model.entity.OpenApiEntity;
import com.zhanglinwei.openApiClient.model.response.OpenApiResponse;
import com.zhanglinwei.openApiClient.segment.*;
import com.zhanglinwei.openApiClient.utils.SQLHelper;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.zhanglinwei.common.constants.StringPoolConstant.*;
import static com.zhanglinwei.openApiClient.constants.OpenApiConstant.WRAPPER_PARAM;

@Getter
public abstract class AbstractWrapper<T extends OpenApiEntity<? extends OpenApiResponse>, R, W extends AbstractWrapper<T, R, W>> implements SQLWrapper<R, W> {

    protected AtomicInteger paramKeySequence = new AtomicInteger(0);

    private Class<T> entityClass;
    private String entityClassFullPath;
    protected Long pageOn;
    protected Long pageSize;

    protected SelectSegment selectSegment = new SelectSegment();
    protected WhereSegment whereSegment = new WhereSegment();
    protected GroupBySegment groupBySegment = new GroupBySegment();
    protected HavingSegment havingSegment = new HavingSegment();
    protected OrderBySegment orderBySegment = new OrderBySegment();

    protected Map<String, Object> parameterMap = new HashMap<>();

    public void setEntityClass(Class<T> entityClass) {
        if (entityClass != null) {
            this.entityClass = entityClass;
            this.entityClassFullPath = entityClass.getCanonicalName();
        }
    }

    @Override
    public W limit(boolean condition, Number pageNo, Number pageSize) {
        if (condition) {
            doLimit(pageNo, pageSize);
        }
        return (W) this;
    }

    @Override
    public W select(boolean condition, Collection<R> columns) {
        if (condition && CollectionUtils.isNotEmpty(columns)) {
            columns.forEach(column -> selectSegment.add(() -> SQLHelper.pascalToSnakeCase(columnToString(column))));
        }

        return (W) this;
    }

    @Override
    public W distinct(boolean condition) {
        if (condition) {
            this.selectSegment.setSelectDistinct(true);
        }

        return (W) this;
    }

    @Override
    public W selectCount(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.COUNT, column, alias);
    }

    @Override
    public W selectDistinctCount(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.DISTINCT_COUNT, column, alias);
    }

    @Override
    public W selectSum(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.SUM, column, alias);
    }

    @Override
    public W selectAvg(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.AVG, column, alias);
    }

    @Override
    public W selectMin(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.MIN, column, alias);
    }

    @Override
    public W selectMax(boolean condition, R column, String alias) {
        return addFunctionSelect(condition, SQLFunction.MAX, column, alias);
    }

    @Override
    public W or(boolean condition, Consumer<W> consumer) {
        return addWhereNestCondition(condition, SQLKeyword.OR, consumer);
    }

    @Override
    public W or(boolean condition) {
        return addLogicOperator(condition, SQLKeyword.WHERE, SQLKeyword.OR);
    }

    @Override
    public W and(boolean condition, Consumer<W> consumer) {
        return addWhereNestCondition(condition, SQLKeyword.AND, consumer);
    }

    @Override
    public W and(boolean condition) {
        return addLogicOperator(condition, SQLKeyword.WHERE, SQLKeyword.AND);
    }

    @Override
    public W eq(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.EQ, val);
    }

    @Override
    public W ne(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.NE, val);
    }

    @Override
    public W gt(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.GT, val);
    }

    @Override
    public W ge(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.GE, val);
    }

    @Override
    public W lt(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.LT, val);
    }

    @Override
    public W le(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.LE, val);
    }

    @Override
    public W between(boolean condition, R column, Object val1, Object val2) {
        return addWhereCondition(condition, column, SQLKeyword.BETWEEN, Arrays.asList(val1, val2));
    }

    @Override
    public W notBetween(boolean condition, R column, Object val1, Object val2) {
        return addWhereCondition(condition, column, SQLKeyword.NOT_BETWEEN, Arrays.asList(val1, val2));
    }

    @Override
    public W like(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.LIKE, "%" + val + "%");
    }

    @Override
    public W likeLeft(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.LIKE, "%" + val);
    }

    @Override
    public W likeRight(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.LIKE, val + "%");
    }

    @Override
    public W notLike(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.NOT_LIKE, "%" + val + "%");
    }

    @Override
    public W notLikeLeft(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.NOT_LIKE, "%" + val);
    }

    @Override
    public W notLikeRight(boolean condition, R column, Object val) {
        return addWhereCondition(condition, column, SQLKeyword.NOT_LIKE, val + "%");
    }

    @Override
    public W isNull(boolean condition, R column) {
        return addWhereCondition(condition, column, SQLKeyword.IS_NULL, null);
    }

    @Override
    public W isNotNull(boolean condition, R column) {
        return addWhereCondition(condition, column, SQLKeyword.IS_NOT_NULL, null);
    }

    @Override
    public W in(boolean condition, R column, Collection<?> coll) {
        return addWhereCondition(condition, column, SQLKeyword.IN, coll);
    }

    @Override
    public W notIn(boolean condition, R column, Collection<?> coll) {
        return addWhereCondition(condition, column, SQLKeyword.NOT_IN, coll);
    }

    @Override
    public W groupBy(boolean condition, Collection<R> columns) {
        return addGroupBy(condition, columns);
    }

    @Override
    public W orderBy(boolean condition, boolean isAsc, Collection<R> columns) {
        return addOrderBy(condition, isAsc, columns);
    }

    @Override
    public W having(boolean condition, Consumer<W> consumer) {
        return addHavingCondition(condition, consumer);
    }

    @Override
    public String previewSql() {
        return SQLHelper.wrapperToSQL(this, true);
    }

    protected String columnToString(R column) {
        return (String) column;
    }

    protected abstract W newInstance();
    public boolean isEmpty() {
        return selectSegment.isEmpty() &&
                whereSegment.isEmpty() &&
                groupBySegment.isEmpty() &&
                havingSegment.isEmpty() &&
                orderBySegment.isEmpty() &&
                (pageOn == null && pageSize == null);
    }

    public boolean isNotEmpty() {
        return !isEmpty();
    }

    private W addFunctionSelect(boolean condition, SQLFunction sqlFunction, R column, String alias) {
        if (condition) {
            String columnName = SQLHelper.pascalToSnakeCase(columnToString(column));
            selectSegment.addFunctionSelect(new SelectSegment.FunctionSelectItem(sqlFunction, columnName, alias));
        }

        return (W) this;
    }

    protected void doLimit(Number pageOn, Number pageSize) {
        this.pageOn = SQLHelper.handlePageOn(pageOn);
        this.pageSize = SQLHelper.handlePageSize(pageSize);
    }

    protected W addLogicOperator(boolean condition, SQLKeyword keyword, SQLKeyword operator) {
        if (condition) {
            if (keyword.isWhere() && whereSegment.isNotEmpty()) {
                whereSegment.removeLastLogicOperator();
                whereSegment.add(operator);
            }
            if (keyword.isHaving() && havingSegment.isNotEmpty()) {
                havingSegment.removeLastLogicOperator();
                havingSegment.add(operator);
            }
        }

        return (W) this;
    }
    
    protected W addGroupBy(boolean condition, Collection<R> columns) {
        if (condition && CollectionUtils.isNotEmpty(columns)) {
            columns.forEach(column -> groupBySegment.add(() -> SQLHelper.pascalToSnakeCase(columnToString(column))));
        }
        
        return (W) this;
    }

    protected W addOrderBy(boolean condition, boolean isAsc, Collection<R> columns) {
        if (condition && CollectionUtils.isNotEmpty(columns)) {
            columns.forEach(column -> {
                orderBySegment.add(new OrderBySegment.OrderByItem(SQLHelper.pascalToSnakeCase(columnToString(column)), isAsc));
            });
        }

        return (W) this;
    }

    private W addHavingCondition(boolean condition, Consumer<W> consumer) {
        if (condition) {
            W wrapperInstance = newInstance();
            consumer.accept(wrapperInstance);
            WhereSegment whereSegment = wrapperInstance.getWhereSegment();
            setEntityClass(wrapperInstance.getEntityClass());

            if (whereSegment.isNotEmpty()) {
                whereSegment.removeLastLogicOperator();
                havingSegment.addAll(whereSegment);
                addLogicOperator(true, SQLKeyword.HAVING, SQLKeyword.AND);
            }
        }

        return (W) this;
    }

    protected W addWhereCondition(boolean condition, R column, SQLKeyword sqlKeyword, Object val) {
        if (!condition) {
            return (W) this;
        }

        String columnName = SQLHelper.pascalToSnakeCase(columnToString(column));
        whereSegment.add(() -> columnName, sqlKeyword);

        if (sqlKeyword.isInCondition()) {
            handleInCondition(val);
        } else if (sqlKeyword.isBetweenCondition()) {
            handleBetweenCondition(val);
        } else if (!sqlKeyword.isOnlyKeywordCondition()) {
            whereSegment.add(() -> formatParam(val));
        }

        return addLogicOperator(true, SQLKeyword.WHERE, SQLKeyword.AND);
    }

    private void handleInCondition(Object val) {
        AssertUT.isTrue(val instanceof Collection,
                "IN/NOT IN requires Collection, but got " + val.getClass());
        Collection<?> coll = (Collection<?>) val;
        whereSegment.add(
                () -> LEFT_BRACKET,
                () -> coll.stream().map(this::formatParam).collect(Collectors.joining(COMMA)),
                () -> RIGHT_BRACKET
        );
    }

    private void handleBetweenCondition(Object val) {
        AssertUT.isTrue(val instanceof Collection, "BETWEEN/NOT BETWEEN requires Collection, but got " + val.getClass());
        List<?> paramList = new ArrayList<>((Collection<?>) val);
        AssertUT.isTrue(paramList.size() == 2, "BETWEEN/NOT BETWEEN requires 2 values, but got " + paramList.size());
        whereSegment.add(
                () -> formatParam(paramList.get(0)),
                SQLKeyword.AND,
                () -> formatParam(paramList.get(1))
        );
    }

    protected String formatParam(Object param) {
        String paramKey = WRAPPER_PARAM + paramKeySequence.incrementAndGet();
        parameterMap.put(paramKey, SQLHelper.paramToString(param));
        return SQLHelper.safeParam(paramKey);
    }

    private W addWhereNestCondition(boolean condition, SQLKeyword sqlKeyword, Consumer<W> consumer) {
        if (condition) {
            W wrapperInstance = newInstance();
            consumer.accept(wrapperInstance);
            WhereSegment groupWhereSegment = wrapperInstance.getWhereSegment();
            setEntityClass(wrapperInstance.getEntityClass());

            if (groupWhereSegment.isNotEmpty()) {
                groupWhereSegment.removeLastLogicOperator();
                whereSegment.removeLastLogicOperator();

                if (whereSegment.isNotEmpty()) {
                    whereSegment.add(sqlKeyword);
                }

                whereSegment.add(() -> LEFT_BRACKET);
                whereSegment.addAll(groupWhereSegment);
                whereSegment.add(() -> RIGHT_BRACKET);

                return addLogicOperator(true, SQLKeyword.WHERE, SQLKeyword.AND);
            }
        }

        return (W) this;
    }

}
